home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 1833 / 1833.xpi / modules / yoonoBkmSync.js next >
Text File  |  2009-12-16  |  39KB  |  1,446 lines

  1. var EXPORTED_SYMBOLS = ["YOONO_BKM"];
  2.  
  3. Components.utils.import("resource://yoono/yoonoPrefs.js");
  4.  
  5. var yoono = {};
  6. var log = {info:function() {},debug:function() {},warn:function(){},error:function (){},fatal:function(){}};
  7.  
  8. // Globals const
  9. var CI = Components.interfaces;
  10. var CL = Components.classes;
  11. const OBS=CL['@mozilla.org/observer-service;1'].getService(CI.nsIObserverService);
  12.  
  13. const DIRSERVICE = CL['@mozilla.org/file/directory_service;1'].getService(CI.nsIProperties);
  14.  
  15. const MAX_PATH_LENGTH = 5000;
  16. const LIVETAG = '.LIV';
  17.  
  18. const TOOLBARNAME = "PERSONAL_BOOKMARK_FOLDER";
  19.  
  20. const PRIVATEFILE = "yoonoprivatefolders.txt";
  21. const YOONO_DIR   = "yoono";
  22. const BACKUP_DIR  = "yoonobookmarkbackups";
  23. const HISTORYPREF  = "extensions.yoono.bookmarks.maxhistory";
  24.  
  25. const BACKUPS_NUMBER = 5;
  26.  
  27. const PREFS  = CL['@mozilla.org/preferences-service;1'].getService(CI.nsIPrefBranch);
  28.  
  29. /*** Globals var ***/
  30. var gCurrentTree = null;
  31. var gCurrentNode = null;
  32. var nbBkms = 1;
  33. var gBkmById = {};
  34. var gBkmByUrl = {};
  35.  
  36.  
  37.  
  38. /***************************************************/
  39. /*** Browser specific version Javascript loading ***/
  40. var restore={};
  41. try {
  42.     // Load version specific JS from : chrome/$ff-version$/*
  43.     var loader = CL["@mozilla.org/moz/jssubscript-loader;1"].createInstance(CI.mozIJSSubScriptLoader);
  44.     loader.loadSubScript("chrome://yoonospecific/content/bookmarks.js");
  45.     loader.loadSubScript("chrome://yoonospecific/content/bookmarks-restore.js",restore);
  46. } catch(e) {
  47.     var console = Components.classes["@mozilla.org/consoleservice;1"].getService(Components.interfaces.nsIConsoleService);
  48.     var scriptError = CL["@mozilla.org/scripterror;1"].createInstance(CI.nsIScriptError);
  49.     scriptError.init("Error loading specific code >>> "+e+" <<<\n"+e.stack, e.filename, null, e.lineNumber, null, scriptError.errorFlag, "");
  50.     console.logMessage(scriptError);
  51. }
  52.  
  53.  
  54.  
  55. function Bookmarks() {
  56.     this.wrappedJSObject=this;
  57. }
  58.  
  59. /********************************/
  60. /*** Component initialization ***/
  61. Bookmarks.prototype.start = function (y) {
  62. try {
  63.     yoono=y;
  64.     log=y.log;
  65.     
  66.     log.debug("load bookmarks component");
  67.     
  68.     function launch() {
  69.         privateMgr.init();
  70.         
  71.         // User already registered and not anonymous
  72.         if (YOONO_PREFS.get('userid')) {
  73.       var aIsModified = YOONO_PREFS.storeLastModified("init");
  74.       var aSyncMode = ( aIsModified == true ? "manual-sync" : "auto-sync" );
  75.       if (YOONO_PREFS.get('nosynchro')) aSyncMode = 'no-sync';
  76.       if(-1 == YOONO_PREFS.get('userid').indexOf(':')) {
  77.         aSyncMode = 'no-sync';
  78.       }
  79.       yoono.server.launch('connect',aSyncMode); // on se connecte apres avoir charge les marque-pages
  80.         }
  81.         // Disable backup on each firefox startup
  82.         //yoono.bkm.backup();
  83.     }
  84.     
  85.     this.initBackups();
  86.     
  87.     // Launch version specific loading
  88.     start(launch);
  89.     
  90. } catch(e) {
  91.     log.exception(e);
  92.     this.dump();
  93. }
  94. }
  95.  
  96. Bookmarks.prototype.uninstall = function () {
  97.   privateMgr.uninstall();
  98. }
  99.  
  100. /*
  101. Bookmarks.prototype.mergeBookmarksWithServer = function() {
  102.   YOONO_PREFS.set('synchroaction', "merge");
  103.   // The load-all-links command is always processed last, whatever its position in the request
  104.   yoono.server.launch('load-all-links');
  105. }
  106.  
  107. Bookmarks.prototype.importBookmarksFromServer = function() {
  108.   YOONO_PREFS.set('synchroaction', "import");
  109.   // The load-all-links command is always processed last, whatever its position in the request
  110.   yoono.server.launch('load-all-links');
  111. }
  112.  
  113. Bookmarks.prototype.exportBookmarksOnServer = function() {
  114.   yoono.server.launch('save-all-links');
  115. }
  116. */
  117.  
  118. /***********************/
  119. /*** Backups service ***/
  120. Bookmarks.prototype.initBackups = function () {
  121.     // Search backup dir
  122.     this.backupDir = DIRSERVICE.get("ProfD",CI.nsIFile);
  123.     this.backupDir.append(BACKUP_DIR);
  124.     if(!this.backupDir.exists()) 
  125.         this.backupDir.create(CI.nsIFile.DIRECTORY_TYPE, 0755);
  126. }
  127. Bookmarks.prototype.setMaxBackups = function (n) {
  128.  
  129.     PREFS.setIntPref(HISTORYPREF,n);
  130.     this.flushBackups();
  131.     
  132. }
  133. Bookmarks.prototype.getMaxBackups = function () {
  134.     return PREFS.getIntPref(HISTORYPREF);
  135. }
  136. Bookmarks.prototype.flushBackups = function () {
  137.     // Remove one file, if we reach backups limit
  138.     while(true) {
  139.         var e = this.backupDir.directoryEntries;
  140.         var older=null;
  141.         var nbFiles=0;
  142.         while(e.hasMoreElements()) {
  143.             var file=e.getNext().QueryInterface(Components.interfaces.nsIFile);
  144.             if (!file.isFile()) continue;
  145.             if (!older || file.lastModifiedTime < older.lastModifiedTime) {
  146.                 older=file;
  147.             }
  148.             nbFiles++;
  149.         }
  150.         if (nbFiles>this.getMaxBackups())
  151.             older.remove(false);
  152.         else
  153.             break;
  154.     }
  155. }
  156. Bookmarks.prototype.backup = function () {
  157. try {    
  158.     // Create a new backup file
  159.     var d=new Date();
  160.     var file = this.backupDir.clone();
  161.     file.append(d.getFullYear()+"_"+(d.getMonth()+1)+"_"+d.getDate()+"-"+d.getHours()+"h"+d.getMinutes()+"m"+d.getSeconds()+"s.html");
  162.     
  163.     // Build backup!
  164.     restore.backup(file);
  165.     
  166.     this.flushBackups();
  167. } catch(e) {
  168.     log.exception(e);
  169. }
  170. }
  171. Bookmarks.prototype.restore = function (name) {
  172.     // Disable server synch
  173.     Server2Browser.working=true;
  174.     
  175.     // Launch restore
  176.     log.debug("START RESTORE");
  177.     var file=this.backupDir.clone();
  178.     file.append(name);
  179.     restore.restore(file);
  180.     log.debug("END RESTORE");
  181.     
  182.     // Export all bkms
  183.     yoono.server.launch('save-all-links');
  184.     
  185.     // Reenable server synch
  186.     Server2Browser.working=false;
  187. }
  188. Bookmarks.prototype.getBackupList = function (file) {
  189.     
  190.     var e = this.backupDir.directoryEntries;
  191.     var backups=[]
  192.     while(e.hasMoreElements()) {
  193.         var file=e.getNext().QueryInterface(Components.interfaces.nsIFile);
  194.         if (!file.isFile()) continue;
  195.         backups.push(file);
  196.     }
  197.     backups.sort(function (a,b) {return a.lastModifiedTime - b.lastModifiedTime;});
  198.     
  199.     var list=[];
  200.     for(var i=0;i<backups.length;i++) {
  201.         var file=backups[i];
  202.         var m=file.leafName.match(/(\d+)_(\d+)_(\d+)-(\d+)h(\d+)m(\d+)s\.html/);
  203.         if (m) {
  204.             list.push({file:file, name:file.leafName, date:new Date(m[1],parseInt(m[2]) - 1,m[3],m[4],m[5],m[6])});
  205.         } else
  206.             file.remove(false);
  207.     }
  208.     
  209.     return list;
  210. }
  211.  
  212.  
  213.  
  214. /*** Debuging ***/
  215. Bookmarks.prototype.dump = function () {
  216.  
  217.     log.info("Bookmark tree : \n"+gCurrentTree.toString());
  218.  
  219. }
  220. Bookmarks.prototype.bookmarksNumber = function () {
  221.     
  222.     var c=0;
  223.     for each(var n in gBkmById) {
  224.         if (!n.isFolder()) {
  225.             c++;
  226.         }
  227.     }
  228.     return c;
  229.     
  230. }
  231.  
  232. /************************/
  233. /*** Common interface ***/
  234. Bookmarks.prototype.loadAllLinks = function (links) {
  235. try {
  236.     
  237.     return Server2Browser.loadAllLinks(links);
  238.     
  239. } catch(e) {
  240.     log.exception(e);
  241. }
  242. }
  243.  
  244. Bookmarks.prototype.getSyncId = function () {
  245.     
  246.     return gCurrentTree?gCurrentTree.getSyncid():"";
  247.     
  248. }
  249.  
  250. Bookmarks.prototype.getCurrentTree = function () {
  251.  
  252.     return gCurrentTree;
  253.  
  254. }
  255.  
  256. Bookmarks.prototype.getNbBkms = function () {
  257.     return nbBkms;
  258. }
  259.  
  260. Bookmarks.prototype.isPrivate = function (id) {
  261.     
  262.     if (gBkmById[id])
  263.         return gBkmById[id].isPrivate();
  264.     return false;
  265.     
  266. }
  267. Bookmarks.prototype.addToPrivateList = function (id) {
  268.     privateMgr.addToList(id);
  269. }
  270. Bookmarks.prototype.removeFromPrivateList = function (id) {
  271.     privateMgr.removeFromList(id);
  272. }
  273.  
  274. Bookmarks.prototype.getNode = function (id) {
  275.     return gBkmById[id];
  276. }
  277.  
  278.  
  279. /**************************/
  280. /*** Specific interface ***/
  281. // TODO : check utility of each function
  282. // Returns full boookmark list as an array
  283. Bookmarks.prototype.getFullListUrl = function () {
  284.     var urlList = [];
  285.     for each (var node in gBkmById) {
  286.         if (node.isFolder() || !node.getUrl())
  287.             continue;
  288.         urlList.push(node.getUrl());
  289.     }
  290.     return urlList;
  291. }
  292. // Returns array of url contained in the folder whose id is passed
  293. Bookmarks.prototype.getNodeListUrl = function(nodeId) {
  294.     var node = this.getNode(nodeId);
  295.   if (node)
  296.     return(this.getUrlsInSubFolder(node));
  297.   else // we may select places top container "all bookmarks" which are not tracked by this component
  298.     return this.getFullListUrl();
  299. }
  300. Bookmarks.prototype.getUrlsInSubFolder = function(node) {
  301.     var obj = {
  302.         list : [],
  303.         onNode : function (node) {
  304.             if(!node.isFolder()) {
  305.                 this.list.push(node.getUrl());
  306.             }
  307.         }
  308.     }
  309.     node.traverseNode(obj, 'force');
  310.     return obj.list;
  311. }
  312.  
  313. Bookmarks.prototype.isKnownUrl = function (url) {
  314.     if (gBkmByUrl[url])
  315.       return true;
  316.     else
  317.       return false;
  318. }
  319.  
  320. Bookmarks.prototype.addBkmAtRoot = function (url, title) {
  321.     gBrowserBkm.createBookmark(title, url, gCurrentTree);
  322. }
  323.  
  324. /******************************/
  325. /*** Old code compatibility ***/
  326. // TODO : remove all call
  327. Bookmarks.prototype.getNodesForTreeView = function (node, level) {
  328.  
  329.     log.backtrace("Deprecated call!");
  330.     // initialisation
  331.     if (!node)
  332.         var node = gCurrentTree;
  333.     if (!node)
  334.         return {open:false, name:'none', children: []};
  335.     if (!level)
  336.         var level = 0;
  337.  
  338.     var obj = new Object();
  339.     obj.open = false;
  340.     obj.level = level;
  341.     obj.node = node;
  342.     obj.published = false;
  343.     obj.origPublished = false; // to save original state to detect changes
  344.     obj.private = node.isPrivate();
  345.     obj.parent = null;
  346.     obj.origPrivate = node.isPrivate(); // to save original state to detect changes
  347.     obj.id = BKMSERV.bookmarksMenuFolder;
  348.     obj.children = [];
  349.  
  350.     return obj;
  351. }
  352. // END of public interface! //
  353. //////////////////////////////
  354.  
  355.  
  356. /*
  357. // TODO : make BkmsObserver specific to FF3 
  358. Browser2Server = {
  359.     notifyServer : function( cmd, arg ) {
  360.         if (Server2Browser.isWorking())    return;
  361.         
  362.         yoono.server.addToNextCommandScript(command, arg);
  363.     },
  364.     itemAdded : function (node) {
  365.         if (node.isAcceptable() && !node.isFolder())
  366.             this.notifyServer('add-link', node);
  367.     },
  368.     itemRemoved : function (node) {
  369.         if (node.isAcceptable())
  370.             this.notifyServer('remove-link', node);
  371.     },
  372.     itemChanged : function(oldNode, newNode) (
  373.         if (newNode.update() && newNode.isAcceptable())
  374.             this.notifyServer('update-link', [oldNode, newNode]);
  375.     },
  376.     itemMoved : function(node) {
  377.     
  378.     }
  379. };
  380. */
  381.  
  382. ////////////////////////////
  383. // BkmsObserver
  384. // :: observe bookmarks modifications
  385. var BkmsObserver = {
  386.     _working : false,
  387.     
  388.     onBeginUpdateBatch: function() {
  389.         log.debug("Begin update batch");
  390.         this._working = true;
  391.     },
  392.     onEndUpdateBatch: function() {
  393.         log.debug("End update Batch");
  394.         this._working = false;
  395.     },
  396.     notifyServer : function(command, node) {
  397.         if (Server2Browser.isWorking())    return;
  398.         
  399.         if (this._working) {
  400.             yoono.server.addToNextCommandScript(command, node);
  401.         } else {
  402.             yoono.server.launch(command, node);
  403.         }
  404.                     
  405.     },
  406.     onItemAdded: function(id, folder, index) {
  407.         
  408.         //log.debug("add item id : "+id);
  409.         
  410.         var parent=gBkmById[folder];
  411.         if (!parent) {
  412.             //log.debug("Ignore bookmarks with parent not in our tree");
  413.             return ;
  414.         }
  415.         
  416.         if (parent.isLive()) {
  417.             //log.debug("we ignore subitem of live bookmarks");
  418.             return;
  419.         }
  420.         
  421.         if (gBkmById[id]) {
  422.             var node=gBkmById[id];
  423.         } else {
  424.             var node=new bkmNode(id, parent);
  425.             parent.addChild(node);
  426.         }
  427.         //log.debug("parent : "+parent.getName()+" name:"+node.getName()+" isFolder:"+node.isFolder());
  428.         
  429.         
  430.         // Delay creation, because livemarks are really livemarks some times later :/ (ie LIVESERV.isLivemark()->true) [FF3]
  431.         // (they are first folder, then livemark)
  432.         // (they are valid livemarks when we receive itemChanged on feedURI property)
  433.         if (!Server2Browser.isWorking()) {
  434.             var _this=this;
  435.             var callback = {
  436.                 notify : function(timer) {
  437.                     try {
  438.                         if (node.isAcceptable() && !node.isFolder()) // we must delay isAcceptable because we check isFolder into...
  439.                             _this.notifyServer('add-link', node);
  440.                     } catch(e) {
  441.                         log.exception(e);
  442.                     }
  443.                 }
  444.             };
  445.             var timer = CL['@mozilla.org/timer;1'].createInstance(CI.nsITimer);
  446.             timer.initWithCallback(callback, 1000 , timer.TYPE_ONE_SHOT);
  447.         }
  448.         return node;
  449.         
  450.     },
  451.     
  452.     onItemRemoved: function(id, folder, index) {
  453.     try {
  454.         //log.debug("remove item : "+id);
  455.         var node=gBkmById[id];
  456.         if (!node) {
  457.             //return log.debug("removed node not tracked");
  458.             return;
  459.         }
  460.         if (node.isFolder()) {
  461.             var children = node.getAllChildren();
  462.             for each(var child in children) {
  463.                 child.remove();
  464.                 if (child.isAcceptable())
  465.                     this.notifyServer('remove-link', child);
  466.             }
  467.         } else {
  468.             if (node.isAcceptable())
  469.                 this.notifyServer('remove-link', node);
  470.         }
  471.         node.remove();
  472.     } catch(e) {
  473.         log.exception(e);
  474.         yoono.bkm.dump();
  475.     }
  476.     },
  477.     
  478.     onItemMoved: function(id, oldParent, oldIndex, newParent, newIndex) {
  479.     try {
  480.         if (oldParent == newParent)
  481.             return log.debug("order moves are ignored");
  482.         log.debug("move item : "+id+" parentId:"+newParent);
  483.         
  484.         var node=gBkmById[id];
  485.         
  486.         // For unknown bookmark, check if destination is known
  487.         if (!node) {
  488.             if (gBkmById[newParent]) {
  489.                 return this.onItemAdded(id, newParent, newIndex);
  490.             } else {
  491.                 return log.debug("moved node not tracked (may be in private)");
  492.             }
  493.         }
  494.         
  495.         log.debug("oldparent:"+oldParent+" > newparent:"+newParent);
  496.         
  497.         if (node.isFolder()) {
  498.             // First save a copy of old children
  499.             var childs=[];
  500.             node.traverseNode({onNode:function(child){
  501.                     if (!child.isFolder())
  502.                         childs.push(child.clone());
  503.                 }});
  504.             // Update originals
  505.             node.getParent().removeChild(node);
  506.             var dest=gBkmById[newParent];
  507.             dest.addChild(node);
  508.             node.update(dest);
  509.             // Send requests
  510.             for each(var child in childs) {
  511.                 var newChild=gBkmById[child.getId()];
  512.                 log.debug("oldpath:"+child.getPath().toServer+" > newpath: "+newChild.getPath().toServer);
  513.                 if (newChild.isAcceptable())
  514.                     this.notifyServer('update-link', [child, newChild]);
  515.             }
  516.         } else {
  517.             var oldNode=node.clone();
  518.             node.getParent().removeChild(node);
  519.             var dest=gBkmById[newParent];
  520.             dest.addChild(node);
  521.             node.update(dest);
  522.             log.debug("oldpath:"+oldNode.getPath().toServer+" > newpath: "+node.getPath().toServer);
  523.             if (node.isAcceptable())
  524.                 this.notifyServer('update-link', [oldNode, node]);
  525.         }
  526.         
  527.     } catch(e) {
  528.         log.exception(e);
  529.         yoono.bkm.dump();
  530.     }
  531.     },
  532.     
  533.     onItemChanged: function(id, property, isAnnotationProperty, value) {
  534.     try {
  535.         if ( ['title', 'uri', 'livemark/feedURI'].indexOf(property) == -1 ) {
  536.             //log.info("only title, uri and feedURI properties are tracked (not "+property+")"+id+" > "+(gBkmById[id]?gBkmById[id].isFolder():"/"));
  537.             return;
  538.         }
  539.         //log.debug("change item : "+id+"/"+property+"/"+isAnnotationProperty+"/"+value);
  540.         var node=gBkmById[id];
  541.         
  542.         
  543.         
  544.         //////
  545.         if (node.isFolder() && property=="title") {
  546.             // First save a copy of old children
  547.             var childs=[];
  548.             node.traverseNode({onNode:function(child){
  549.                     if (!child.isFolder())
  550.                         childs.push(child.clone());
  551.                 }});
  552.             // Update originals
  553.             node.update();
  554.             // Send requests
  555.             for each(var child in childs) {
  556.                 var newChild=gBkmById[child.getId()];
  557.                 log.debug("oldpath:"+child.getPath().toServer+" > newpath: "+newChild.getPath().toServer);
  558.                 if (newChild.isAcceptable())
  559.                     this.notifyServer('update-link', [child, newChild]);
  560.             }
  561.         } else {
  562.             var oldNode=node.clone();
  563.             if (node.update() && node.isAcceptable()) {
  564.                 // See onItemAdded ...
  565.                 if (property=="livemark/feedURI" && !oldNode.getUrl())
  566.                     return log.debug("we ignore first livemark url notification");
  567.                 this.notifyServer('update-link', [oldNode, node]);
  568.             } else 
  569.                 log.error("not acceptable ? no update ? ");
  570.         }
  571.         
  572.     } catch(e) {
  573.         log.exception(e);
  574.         yoono.bkm.dump();
  575.     }
  576.     },
  577.     onItemVisited: function(id, visitID, time) {
  578.         
  579.     },
  580.     
  581.     QueryInterface: function(iid) {
  582.         if (iid.equals(Ci.nsINavBookmarkObserver) ||
  583.             iid.equals(Ci.nsISupports)) {
  584.             return this;
  585.         }
  586.         throw Components.results.NS_ERROR_NO_INTERFACE;
  587.     }
  588.  
  589. };
  590. //////////////////////////////
  591.  
  592.  
  593.  
  594.  
  595.  
  596.  
  597. //////////////////////////////
  598. // Server2Browser
  599. // :: handle info from server and sync with rdf view
  600. // :: initial loading of all bookmarks from server
  601. var Server2Browser = {
  602.     isWorking : function() {return this.working;},
  603.     // la liste des marque-pages pr∩┐╜sents dans l'ordi
  604.     notOnServer : {},
  605.     // une liste des chemins deja calcules
  606.     foldersList : {},
  607.     // list of incoming private folders
  608.     privateIncomingFoldersList : {},
  609.     // List of folders that will have to be added to private folder file
  610.     newPrivateFolders : {},
  611.     // List of links that where in a browser private folder that the synchro sets public
  612.     linksBecomingPublic : {},
  613.  
  614.     /*
  615.     * List all private folders marked as such in incoming folder list
  616.     */
  617.     listPrivateIncomingFolders : function (linkList) {
  618.         var key = '';
  619.         for each (var addLink in linkList) {
  620.             var privateFolder = addLink.@['private'].toString();
  621.             if('CLIENT' == privateFolder) {
  622.                 key = addLink.@path.toString();
  623.                 log.debug('Private incoming folder found : ' + key);
  624.                 this.privateIncomingFoldersList[key] = {exists: false} ;
  625.             }
  626.         }
  627.     },
  628.  
  629.     /*
  630.     * manages the list of folders that where in a browser private folder that the synchro
  631.     * now sets to public
  632.     * @param node: folder that becomes public
  633.     */
  634.     addLinksBecomingPublic : function(node) {
  635.         var add = {
  636.             onNode : function() {}
  637.         };
  638.         add.onNode = function(node) {
  639.             if (node.isFolder() )
  640.                 return;
  641.             var key = node.getPath().toServer + node.getUrl();
  642.             // log.debug("XXX BECOMING PUBLIC : " + key);
  643.             Server2Browser.linksBecomingPublic[key] = {
  644.                 'node' : node,
  645.                 'mustCreate' : false
  646.             };
  647.         };
  648.         node.traverseNode(add);
  649.     },
  650.  
  651.     /*
  652.     * @param linkList : liste des elements a ajouter a l'arborescence (object xmlList)
  653.     */
  654.     loadAllLinks : function (linkList) {
  655.         
  656.         log.debug('begin loadAllLinks');
  657.         this.foldersList = {};
  658.         this.privateIncomingFoldersList = {};
  659.         this.newPrivateFolders = {};
  660.         this.linksBecomingPublic = {};
  661.         this.notOnServer = {};
  662.         this.working = true;
  663.         
  664.         var _this=this;
  665.         gBrowserBkm.runInBatchMode(function () {
  666.                 _this.loadAllInBatched(linkList);
  667.             });
  668.         
  669.         return this.resp;
  670.     },
  671.     loadAllInBatched : function(linkList) {
  672.         // on renvoie l'action effectivement effectu∩┐╜e, et le nombre de marque-pages import∩┐╜s
  673.         this.resp = { 
  674.             action : YOONO_PREFS.get('synchroaction'),
  675.             // nombre reellement crees
  676.             added : 0,
  677.             // importes depuis le serveur
  678.             imported : 0,
  679.             clearCommands : true
  680.         };
  681.         
  682.         // List can contain (empty) incoming private folders just to be able not to erase them from the browser
  683.         // in case they are not marked private yet (sync...)
  684.         // They have the private="CLIENT" attribute. Parse the list to find them and set them as private
  685.         this.listPrivateIncomingFolders(linkList);
  686.  
  687.         // on pousse tous les noeuds existants dans notOnServer pour detecter les suppressions a faire
  688.         // (except private folders, either existing or incoming)
  689.         var aNotOnServerCount = 0;
  690.         for each (var node in gBkmById) {
  691.             if (node.isFolder() ) {
  692.                 var path = node.getPath().toServer;
  693.                 // If private folder, not in the incoming private folders list, set it to public
  694.                 if(!this.privateIncomingFoldersList[path] && node.isPrivate()) {
  695.                     log.debug('Path ' + path + ' is now public on server');
  696.                     var id = node.getId();
  697.                     // Remove it from list of private folders
  698.                     privateMgr.removeFolderFromList(id);
  699.                     node.setPrivate(false);
  700.                     // This folder may have content that is not yet on the server (added when it was private)
  701.                     // and that must be added, excluding what is already on the server... what a mess :-)
  702.                     // Store its content in a special list that will be used with the notOnServer list to build
  703.                     // the add-link orders (and to leave them on the computer !)
  704.                     this.addLinksBecomingPublic(node);
  705.                 }
  706.             }
  707.  
  708.             // skip private nodes, and other stuff
  709.             if (!node.isAcceptable()) {
  710.                 continue;
  711.             }
  712.             if (node.isFolder() ) {
  713.                 this.foldersList[node.getPath().toServer] = node;
  714.             } else {
  715.                 var key = node.getPath().toServer + node.getUrl();
  716.                 if (!this.notOnServer[key]) {
  717.                     this.notOnServer[key] = new Array(); // array to manage duplicates
  718.                 }
  719.                 this.notOnServer[key].push(node);
  720.                 aNotOnServerCount++;
  721.             }
  722.         }
  723.  
  724.         // Send a global notifications that we are starting a synchro
  725.         OBS.notifyObservers (this,"yoono-synchro-start",YOONO_PREFS.get('synchroaction'));
  726.         yoono.bkm.backup();
  727.  
  728.         log.debug('notOnServer.length : '+aNotOnServerCount);
  729.  
  730.         this.saveLinks(linkList);
  731.         log.debug('imported='+this.resp.imported+', added='+this.resp.added+', synchroaction='+YOONO_PREFS.get('synchroaction'));
  732.  
  733.         if (YOONO_PREFS.get('synchroaction') == 'import') {
  734.             if(this.resp.imported > 0) {
  735.                 var length = 0;
  736.                 // Must delete from server links that remain in notOnServer
  737.                 for each (var nodeList in this.notOnServer) {
  738.                     length += nodeList.length;
  739.                 }
  740.                 log.debug('length='+length);
  741.                 if (length) {
  742.                     for (var i in this.notOnServer) {
  743.                         log.debug('i='+i);
  744.                         // Remove bkms that were not on server
  745.                         // except if they used to be in a private folder that became public during this sync
  746.                         if(!this.linksBecomingPublic[i]) {
  747.                             for (var j = this.notOnServer[i].length ; j-- > 0; ) {
  748.                                 this.removeBkm(this.notOnServer[i][j]);
  749.                             }
  750.                         } else {
  751.                             // Mark link for addition to server
  752.                             this.linksBecomingPublic[i].mustCreate = true;
  753.                         }
  754.                         delete (this.notOnServer[i]);
  755.                     }
  756.                     
  757.                 }
  758.                 // Create the links that were added to private folders that became public because of the sync
  759.                 log.debug('Adding links that became public :');
  760.                 var cpt = 0;
  761.                 for (var newNode in this.linksBecomingPublic) {
  762.                     if(this.linksBecomingPublic[newNode].mustCreate) {
  763.                         log.debug('Adding ' + newNode);
  764.                         yoono.server.launch('add-link', this.linksBecomingPublic[newNode].node);
  765.                     }
  766.                 }
  767.                 log.debug(cpt + ' links that became public added.');
  768.  
  769.             } else {
  770.                 log.warn("abnormal situation : invalid import, server returns 0 link. Local bookmarks aren't modified");
  771.                 this.resp.clearCommands = false;
  772.             }
  773.         }
  774.  
  775.         // Add to private folder list file the new ones just loaded
  776.         privateMgr.appendToList(this.newPrivateFolders);
  777.         
  778.         aNotOnServerCount=0;
  779.         for(var key in this.notOnServer)
  780.             aNotOnServerCount++;
  781.         
  782.         if (aNotOnServerCount>0)
  783.             yoono.server.launch('save-all-links-add', this.notOnServer);
  784.         
  785.         this.working = false;
  786.         this.notOnServer = {};
  787.         this.foldersList = {};
  788.         
  789.         // Send a global notifications that we are ending a synchro
  790.         // Needed by sidebar Wizard to know when to send bkm sharing information
  791.         OBS.notifyObservers (this,"yoono-synchro-end",YOONO_PREFS.get('synchroaction'));
  792.  
  793.         log.debug('end loadAllLinks');
  794.         return this.resp;
  795.  
  796.     },
  797.  
  798.     /*
  799.     @param linkList : liste d'element de la forme <link title="aTitle" url="aUrl"/>
  800.     cree les liens correspondants
  801.     */
  802.     saveLinks : function(linkList) {
  803.         for each (var addLink in linkList) {
  804.             try {
  805.                 var pathAsStr = addLink.@path.toString();
  806.                 var url = addLink.@url.toString();
  807.                 var title = addLink.@title.toString();
  808.                 var pos = title.lastIndexOf(LIVETAG);
  809.                 if (pos != -1 && pos == title.length - LIVETAG.length) {
  810.                     title = title.slice(0, -LIVETAG.length);
  811.                     var type = 'l';
  812.                 } else {
  813.                     var type = 'b';
  814.                 }
  815.                 if (title.match(/^NOTITLE-/))
  816.                     title = '';
  817.  
  818.                 var key = pathAsStr + url;
  819.                 if (this.notOnServer[key]) {
  820.                     // on prend le premier
  821.                     var node = this.notOnServer[key][0];
  822.                     // si on recoit une url deja presente avec un titre different,
  823.                     // on ecrase le titre existant au lieu de dupliquer le mp
  824.                     
  825.                     if (node.getName() != title) {
  826.                         gBrowserBkm.rename(node,title);
  827.                     }
  828.                     // on supprime les doublons
  829.                     for (var i = this.notOnServer[key].length ; i-- > 1; ) {
  830.                         this.removeBkm(this.notOnServer[key][i]);
  831.                     }
  832.                     // Since the bkm exists on the server, remove it from the list
  833.                     delete (this.notOnServer[key]);
  834.                 } else {
  835.                     // new or modified bkm
  836.                     this.checkOrCreateBkm(addLink, pathAsStr, title, url, type);
  837.                 }
  838.                 
  839.                 this.resp.imported++;
  840.             } catch(e) {
  841.                 log.exception(e);
  842.             }
  843.         }
  844.  
  845.     },
  846.  
  847.     checkOrCreateBkm : function(addLink, pathAsStr, title, url, type) {
  848.         var folder;
  849.         var privateFolder = addLink.@['private'].toString();
  850.         
  851.         var path = yoono.utils.splitPath(pathAsStr);
  852.         
  853.         if ('/' != pathAsStr) {
  854.             if(!(folder = this.foldersList[pathAsStr])) {
  855.                 folder = this.getOrCreateFolder(path);
  856.                 this.foldersList[pathAsStr] = folder;
  857.             }
  858.         } else {
  859.             folder = gCurrentTree;
  860.         }
  861.  
  862.         // If private folder received from server already exists as not private
  863.         if(("CLIENT" == privateFolder) && Server2Browser.privateIncomingFoldersList[pathAsStr] && Server2Browser.privateIncomingFoldersList[pathAsStr].exists) {
  864.             // Record it as 'to be added to private folders'
  865.             var id = folder.getId();
  866.             if('/' == pathAsStr) id = gCurrentTree.id;
  867.             log.debug('New private folder: Path:' + pathAsStr + ' id: ' + id + ' title: ' + title + ' url: ' + url);
  868.             Server2Browser.newPrivateFolders[id] = true;
  869.             // delete it from list so that later 'isAcceptable' calls won't be disturbed...
  870.             // setting it public again would be impossible otherwise
  871.             delete (Server2Browser.privateIncomingFoldersList[pathAsStr]);
  872.         }
  873.  
  874.         //log.info('Checking Path:' + pathAsStr + ' title: ' + title + ' url: <' + url + '> folder : \n'+folder.getName());
  875.         if (!url)
  876.             return false;
  877.         
  878.         for each(var c in folder.getChildren()) {
  879.             if (c.getUrl() == url) {
  880.                 return false;
  881.             }
  882.         }
  883.         
  884.         if (type == 'b') {
  885.             gBrowserBkm.createBookmark( title, url, folder);
  886.         } else if (type == 'l') {
  887.             gBrowserBkm.createLivemark( title, url, folder);
  888.         }
  889.         this.resp.added++;
  890.         return true;
  891.     },
  892.     
  893.     getOrCreateFolder : function(path) {
  894.         if (path.length == 1) {
  895.             return gCurrentTree;
  896.         }
  897.         
  898.         if (path[(path.length - 1)] == TOOLBARNAME && path.length == 2) {
  899.             return gToolbarNode;
  900.         }
  901.         
  902.         var parent = this.getOrCreateFolder(path.slice(0, -1));
  903.         
  904.         var lastFolderName = path[path.length - 1];
  905.         
  906.         for each(var c in parent.getChildren()) {
  907.             if (c.getName() == lastFolderName && c.isFolder()) {
  908.                 return c;
  909.             }
  910.         }
  911.         
  912.         return gBrowserBkm.createFolder(lastFolderName, parent);
  913.     },
  914.     
  915.     removeBkm : function (node) {
  916.         //log.debug('remove node (name='+node.getName()+',path='+node.getPath().toServer + ')');
  917.         var parent = node.getParent();
  918.         
  919.         gBrowserBkm.remove(node);
  920.         
  921.         if (parent.isFolder() && parent.getChildren().length==0)
  922.             gBrowserBkm.remove(parent);
  923.         
  924.     },
  925.     
  926. }
  927. ///////////////////////////////
  928.  
  929.  
  930.  
  931.  
  932. ///////////////////////////////
  933. // bkmNode
  934. // :: each bookmark or folder are mapped to one bkmNode
  935. // :: generate Unique ID
  936.  
  937. // Public common interface
  938. bkmNode.prototype.getId = function () {
  939.     return this.id;
  940. }
  941. bkmNode.prototype.getUrl = function() {
  942.     return this.url;
  943. }
  944. bkmNode.prototype.getParent = function () {
  945.     return this.parent;
  946. }
  947. bkmNode.prototype.getName = function () {
  948.     return this.name;
  949. }
  950. bkmNode.prototype.getChildren = function () {
  951.     return this.childsList;
  952. }
  953. bkmNode.prototype.isPrivate = function () {
  954.     return this.private;
  955. }
  956. bkmNode.prototype.isChildOfPrivate = function () {
  957.     return this.childOfPrivate;
  958. }
  959. // ""Protected"" interface (used only by bookmark service)
  960. bkmNode.prototype.setPrivate = function (newStatus) {
  961.     if (newStatus!=this.private) {
  962.         this.private=newStatus;
  963.     if(this.private) {
  964.       nbBkms --;
  965.     } else {
  966.       nbBkms ++;
  967.     }
  968.         this.resetSyncid();
  969.         function traverse(node) {
  970.             for each(var child in node.childsList) {
  971.                 if (!child.isPrivate()) {
  972.                     child.childOfPrivate=newStatus;
  973.                     traverse(child);
  974.                 }
  975.             }
  976.         }
  977.         traverse(this);
  978.     }
  979. }
  980.  
  981. bkmNode.prototype.addChild = function (child) {
  982.   nbBkms ++ ;    
  983.     this.childsList.push(child);
  984.     this.resetSyncid();
  985.     
  986. }
  987.  
  988. bkmNode.prototype.clone = function () {
  989.     var clone={};
  990.     for(var i in this) {
  991.         clone[i]=this[i];
  992.     }
  993.     return clone;
  994. }
  995.  
  996. bkmNode.prototype.remove = function () {
  997.     
  998.     this.parent.removeChild(this);
  999.     delete gBkmById[this.id];
  1000.   nbBkms --;
  1001.     if (this.isBookmark() || this.isLive())
  1002.         delete gBkmByUrl[this.getUrl()];
  1003.     if (this.isFolder()) {
  1004.         for each(c in this.childsList)
  1005.             c.remove();
  1006.     }
  1007.     delete this.childsList;
  1008.     
  1009. }
  1010.  
  1011. bkmNode.prototype.toString = function (indent) {
  1012.     
  1013.     if (!indent)
  1014.         indent="";
  1015.     if (this.isFolder()) {
  1016.         var str=indent+"[ #"+this.id+" sync:"+this.syncid+" t:"+this.type+", "+(this.private?"private":"public")+" , name:"+this.getName()+"\n";
  1017.         for(var i in this.childsList) {
  1018.             str+=this.childsList[i].toString(indent+"  ");
  1019.         }
  1020.         str+=indent+"]\n";
  1021.         return str;
  1022.     } else {
  1023.         var name=this.getName();
  1024.         return indent+"( #"+this.id+" sync:"+this.syncid+" t:"+this.type+" , name:"+(name?name.substr(0,50):"")+" )\n";
  1025.     }
  1026.     
  1027. }
  1028.  
  1029. bkmNode.prototype.getAllChildren = function () {
  1030.     if (!this.isFolder()) 
  1031.         return [];
  1032.     var children = this.childsList;
  1033.     for(var i in this.childsList) {
  1034.         children=children.concat(this.childsList[i].getAllChildren());
  1035.     }
  1036.     return children;
  1037. }
  1038.  
  1039.     /*
  1040.     * @ return : un objet path comprenant :
  1041.     * .toSync : une chaine qui sera utilisee pour calculer le sync-id
  1042.     * .toServer : une chaine qui sera envoyee au serveur
  1043.     */
  1044. bkmNode.prototype.getPath = function() {
  1045.     if (!this.path) {
  1046.         if (!this.parent) {
  1047.             // fin de la recursion
  1048.             this.path = {
  1049.                     toSync : '/',
  1050.                     toServer : '/',
  1051.                 };
  1052.         } else {
  1053.             var pPath=this.parent.getPath();
  1054.             this.path = {toSync:pPath.toSync, toServer:pPath.toServer};
  1055.             if (this.isFolder()) {
  1056.                 if (this.parent.parent) { // If the parent is not the root node
  1057.                     this.path.toSync += '/';
  1058.                     this.path.toServer += '/';
  1059.                 }
  1060.                 this.path.toSync += yoono.utils.normalize(this.getName()).toLowerCase();
  1061.                 this.path.toServer += yoono.utils.escapePath(this.getName());
  1062.             }
  1063.         }
  1064.     }
  1065.     return this.path;
  1066. }
  1067.  
  1068. bkmNode.prototype.getSyncid = function () {
  1069.     //log.debug(" get syncid : "+this.id+" "+this.syncid+" "+this.name);
  1070.     if (!this.syncid) {
  1071.         var aPath = this.getPath().toSync;
  1072.  
  1073.         if (this.isFolder()) {
  1074.             var str = aPath + 'F';
  1075.             var sum = this.stringToSum(aPath);
  1076.  
  1077.             var urlExist = {};
  1078.  
  1079.             for (var i = this.childsList.length; i--> 0;) {
  1080.                 var item= this.childsList[i];
  1081.                 var url = item.getUrl();
  1082.                 if (item.isAcceptable() && !urlExist[url]) {
  1083.                     sum += item.getSyncid();
  1084.                     if (url) {
  1085.                         urlExist[url] = true;
  1086.                     }
  1087.                 }
  1088.             }
  1089.         } else {
  1090.             var aName = this.getName().substring(0,124);
  1091.             var aHref = this.getUrl();
  1092.             aName = yoono.utils.normalize(aName);
  1093.             if( aHref ) {  // patch pour Firefox 2.0 (microsummaries url=null)
  1094.                 var aUrl = aHref.substring(0,255);
  1095.                 var str = aPath.concat( aName.toLowerCase() , aUrl , 'B');
  1096.                 //var str=[aPath,aName.toLowerCase(),aUrl,'B'].join("");
  1097.                 sum = this.stringToSum(str);
  1098.             }
  1099.         }
  1100.         this.syncid = sum;
  1101.     }
  1102.     return this.syncid;
  1103. }
  1104.  
  1105.  
  1106.  
  1107. var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
  1108.  
  1109. // we use UTF-8 here, you can choose other encodings.
  1110. converter.charset = "UTF-8";
  1111.  
  1112. // Inspiration : http://www.webtoolkit.info/javascript-utf8.html
  1113. bkmNode.prototype.stringToSum = function (str) {
  1114.     var a = 1;
  1115.     var b = 0;
  1116.     var l = 0;
  1117.     
  1118.     var result = {};
  1119.     var data = converter.convertToByteArray(str, result);
  1120.     l=result.value;
  1121.     for (var i = 0/*, j=0*/ ; i < l; /*j++*/) {
  1122.         // Convert from unicode array to "charCodeAt" values
  1123.         var code = -1;
  1124.         if (data[i]<128) {
  1125.             code=data[i];
  1126.             i++;
  1127.         } else if ((data[i] > 191) && (data[i] < 224)) {
  1128.             code=(((data[i] & 31) << 6) | (data[i+1] & 63));
  1129.             i+=2;
  1130.         } else {
  1131.             code=((data[i] & 15) << 12) | ((data[i+1] & 63) << 6) | (data[i+2] & 63);
  1132.             i+=3;
  1133.         }
  1134.         //if (code!=str.charCodeAt(j))
  1135.         //    log.debug(this.id+"@"+i+" : "+str[i]+" -> "+code+"/"+str.charCodeAt(j)+"  ["+str.length+"/"+l+"]");
  1136.         a += code;
  1137.         b += a + code;
  1138.     }
  1139.     return (a % 65521) + 65536 * (b % 65521);
  1140. }
  1141.  
  1142. /*
  1143. bkmNode.prototype.stringToSum= function(buf)
  1144. {
  1145.     var adler = 1;
  1146.     var len = buf.length;
  1147.  
  1148.     var NMAX = 3854;
  1149.     var BASE = 65521;
  1150.  
  1151.     var s1 = adler & 0xffff;
  1152.     var s2 = (adler & 0xffff0000) / 65536;
  1153.     var k;
  1154.     var bpos = 0;
  1155.  
  1156.     while (len > 0) {
  1157.         k = len < NMAX ? len : NMAX;
  1158.         len -= k;
  1159.         while (k > 0) {
  1160.             s1 = (s1 + buf.charCodeAt(bpos)) & 0xffffffff;
  1161.             s2 = (s2 + s1) & 0xffffffff;
  1162.             bpos += 1;
  1163.             k -= 1;
  1164.         }
  1165.         s1 = s1 % BASE;
  1166.         s2 = s2 % BASE;
  1167.     }
  1168.     return 2;
  1169.     return ((s2 & 0x0000ffff)*65536)+s1;
  1170. }
  1171. */
  1172. bkmNode.prototype.removeChild = function(node) {
  1173.     if (!this.isFolder() || !this.childsList)
  1174.         return;
  1175.     for(i=0;i<this.childsList.length;i++){
  1176.         if(node==this.childsList[i]) {
  1177.       this.childsList.splice(i, 1);
  1178.       nbBkms --;
  1179.       break;
  1180.     }
  1181.     }
  1182.     this.resetSyncid();
  1183. }
  1184.  
  1185. bkmNode.prototype.resetSyncid = function () {
  1186.     var pNode = this;
  1187.     while (pNode) {
  1188.         pNode.syncid = 0;
  1189.         pNode = pNode.getParent();
  1190.     }
  1191. }
  1192.  
  1193. bkmNode.prototype.isAcceptable = function () {
  1194.     if (this.getPath().toServer.length > MAX_PATH_LENGTH)
  1195.         return false;
  1196.     if (this.isChildOfPrivate())
  1197.         return false;
  1198.     return true;
  1199.   /*
  1200.     // if folder, check if it exists in incoming private folder list so that it won't
  1201.     // be erased even if wasn't private before import
  1202.     if (this.isFolder()) {
  1203.         var key = this.getPath().toServer;
  1204.         if(Server2Browser.privateIncomingFoldersList[key] && !this.private) {
  1205.             log.debug('Found existing public folder ' + key);
  1206.             Server2Browser.privateIncomingFoldersList[key].exists = true;
  1207.             this.private = true;
  1208.             return false;
  1209.         }
  1210.     }
  1211.     */
  1212.  
  1213. }
  1214.  
  1215. /*
  1216.     * traverse recursivement le noeud
  1217.     * l'objet peut contenir une fonction onNode
  1218.     * a etre appliquee a chaque noeud
  1219.     * par d∩┐╜faut, on n'applique pas cette m∩┐╜thode
  1220.     * ∩┐╜ un noeud non acceptable, mais on peut forcer
  1221. */
  1222. bkmNode.prototype.traverseNode = function (obj, force) {
  1223.     if (!this.isAcceptable() && !force)
  1224.         return;
  1225.     obj.onNode(this);
  1226.     if (this.isFolder()) {
  1227.         for (var i = this.childsList.length ; i-- > 0; ) {
  1228.             this.childsList[i].traverseNode(obj, force);
  1229.         }
  1230.     }
  1231. }
  1232.  
  1233. /*
  1234.     @ param node      : un noeud de l'arborescence
  1235.     @ param xml       : un objet xml
  1236.     @ return          : l'objet xml qui contient les attributs title, path, et url correspondants au noeud
  1237.     xml est modifie dans cette fonction
  1238. */
  1239. bkmNode.prototype.setAllAttributes = function(xml) {
  1240.     this.setAttribute('title' , xml);
  1241.     this.setAttribute('url', xml);
  1242.     this.setAttribute('path', xml);
  1243.     return xml;
  1244. }
  1245.  
  1246. /*
  1247.     @ param node      : un noeud de l'arborescence
  1248.     @ param xml       : un objet xml
  1249.     @ param attribute : une chaine representant l'attribut a traiter (path, url ou title)
  1250.     @ return          : l'objet xml qui contient l'attribut correspondant au noeud
  1251.     xml est modifie dans cette fonction
  1252. */
  1253. bkmNode.prototype.setAttribute = function (attribute, xml) {
  1254.     switch (attribute) {
  1255.     case ('title') :
  1256.         if (this.getName()) {
  1257.             // on met un marqueur sur les live-marks pour pouvoir les reconnaitre lors d'un import
  1258.             xml.@title = this.getName() + ((this.isLive()) ? LIVETAG : '');
  1259.         }
  1260.         break;
  1261.     case ('path') :
  1262.         xml.@path = this.getPath().toServer;
  1263.         break;
  1264.     case ('url') :
  1265.         if (this.getUrl()) {
  1266.             xml.@url = this.getUrl();
  1267.         }
  1268.         break;
  1269.     }
  1270.     return xml;
  1271. }
  1272.  
  1273. ////////////////////////////////////
  1274.  
  1275.  
  1276.  
  1277.  
  1278.  
  1279. //////////////////////////////////////
  1280. // privateMgr
  1281. // :: manage private bookmarks list in %profile%/$YOONO_DIR/$PRIVATEFILE
  1282. // :: Display locks in front of private folders
  1283. // :: Call server to synchronise
  1284. var privateMgr = {
  1285.  
  1286.     file : DIRSERVICE.get("ProfDS", CI.nsIFile),
  1287.  
  1288.     init : function () {
  1289.     try {
  1290.         // Releases before 3.0.6 wrote the locked folders file in the user chrome directory
  1291.         // Newer releases write the locked file in the yoono directory
  1292.         // If the file exists at the old location, move it first
  1293.         var oldFile = DIRSERVICE.get("UChrm", CI.nsIFile);
  1294.         oldFile.append(PRIVATEFILE);
  1295.         if (oldFile.exists()) {
  1296.             var newFile = DIRSERVICE.get('ProfDS', CI.nsIFile);
  1297.             newFile.append(YOONO_DIR);
  1298.             oldFile.moveTo(newFile,PRIVATEFILE);
  1299.         }
  1300.         this.file.append(YOONO_DIR);
  1301.         this.file.append(PRIVATEFILE);
  1302.         this.readList();
  1303.  
  1304.     } catch(e) {
  1305.         log.exception(e);
  1306.     }
  1307.     },
  1308.   uninstall : function () {
  1309.     try {
  1310.       var file = DIRSERVICE.get("ProfDS", CI.nsIFile);
  1311.       file.append(YOONO_DIR);
  1312.       file.append(PRIVATEFILE);
  1313.       if( file.exists() ) {
  1314.         file.remove(true);
  1315.       }
  1316.     } catch(e) {}
  1317.   },
  1318.  
  1319.     appendToList : function (newPrivateFolders) {
  1320.     try {
  1321.         var outputStream = CL["@mozilla.org/network/file-output-stream;1"].createInstance(CI.nsIFileOutputStream);
  1322.         // 0x02 | 0x10 : WRONLY, APPEND or CREATE
  1323.         outputStream.init(this.file, 0x02 | 0x08 | 0x10 , 0644, 0);
  1324.         for (var id in newPrivateFolders) {
  1325.             outputStream.write(id + '\n' , id.length + 1);
  1326.         }
  1327.         outputStream.close();
  1328.     } catch(e) {
  1329.         log.exception(e);
  1330.     }
  1331.     },
  1332.  
  1333.     addToList : function (id) {
  1334.         var node = gBkmById[id];
  1335.         if(!node)
  1336.             return log.error('NULL : ' + id);
  1337.         
  1338.         if (node.isPrivate())
  1339.             return log.debug("node is already private");
  1340.         
  1341.         
  1342.         if (node.isFolder()) {
  1343.             node.traverseNode({onNode:function(child) {
  1344.                     if (!child.isFolder())
  1345.                         yoono.server.launch('remove-link', child);
  1346.                 }});
  1347.             node.setPrivate(true);
  1348.             yoono.server.launch('add-private-folder', node);
  1349.         } else {
  1350.             log.error("add bkm to private list ? (must be called only on folders)");
  1351.         }
  1352.         this.saveList();
  1353.         log.info(node.getName() + ' becomes private');
  1354.     },
  1355.  
  1356.     removeFromList : function (id) {
  1357.         var node = gBkmById[id];
  1358.         if(!node)
  1359.             return log.error('NULL : ' + id) ; 
  1360.         
  1361.         if (!node.isPrivate())
  1362.             return log.error("node is not private : "+id);
  1363.         
  1364.         node.setPrivate(false);
  1365.         log.info(node.getName() + ' set public by user');
  1366.         
  1367.         if (node.isFolder()) {
  1368.             yoono.server.launch('remove-private-folder', node);
  1369.             node.traverseNode({onNode:function(child) {
  1370.                     if (!child.isFolder()) {
  1371.                         yoono.server.launch('add-link', child);
  1372.           }
  1373.                 }});
  1374.         } else {
  1375.             log.error("remove bkm from private list ? (must be called only on folders)");
  1376.         }
  1377.         this.saveList();
  1378.         
  1379.     },
  1380.  
  1381.     /* Called on a private folder that the server says is now public
  1382.     * folder must be removed from private folders list
  1383.     */
  1384.     removeFolderFromList : function (id) {
  1385.         var node = gBkmById[id];
  1386.         node.setPrivate(false);
  1387.         log.info(node.getName() + ' set public by server');
  1388.         this.saveList();
  1389.         node.resetSyncid();
  1390.     },
  1391.  
  1392.     // cette fonction doit etre appelee une fois que les marque-pages ont ete charges
  1393.     readList : function() {
  1394.         if (!this.file.exists()) {
  1395.             return;
  1396.         }
  1397.         var istream = CL["@mozilla.org/network/file-input-stream;1"].createInstance(CI.nsIFileInputStream);
  1398.         istream.init(this.file, 0x01, 0444, 0);
  1399.         istream.QueryInterface(CI.nsILineInputStream);
  1400.         var _uptodate = true;
  1401.         var line = {};
  1402.         var hasmorelines;
  1403.         do {
  1404.             hasmorelines = istream.readLine(line);
  1405.             // on elimine les lignes vides, sources d'erreurs
  1406.             if (line.value) {
  1407.                 // on ne garde que si la ressource existe reelement
  1408.                 if (gBkmById[line.value]) {
  1409.                     gBkmById[line.value].setPrivate(true);
  1410.                     // sinon, on met le fichier a jour
  1411.                 } else {
  1412.                     _uptodate = false;
  1413.                 }
  1414.             }
  1415.         } while(hasmorelines);
  1416.         istream.close();
  1417.         if (!_uptodate) {
  1418.             this.saveList();
  1419.         }
  1420.     },
  1421.  
  1422.     clearList : function() {
  1423.         log.debug('Clearing private folders file');
  1424.         var outputStream = CL["@mozilla.org/network/file-output-stream;1"].createInstance(CI.nsIFileOutputStream);
  1425.         outputStream.init(this.file, 0x02 | 0x08 | 0x20 , 0644, 0);
  1426.         outputStream.close();
  1427.     },
  1428.  
  1429.     saveList : function () {
  1430.         var outputStream = CL["@mozilla.org/network/file-output-stream;1"].createInstance(CI.nsIFileOutputStream);
  1431.         outputStream.init(this.file, 0x02 | 0x08 | 0x20 , 0644, 0);
  1432.  
  1433.         for (var id in gBkmById) {
  1434.             if (gBkmById[id].isPrivate()) {
  1435.                 outputStream.write(id + '\n' , id.length + 1);
  1436.             }
  1437.         }
  1438.         outputStream.close();
  1439.     }
  1440.  
  1441. }
  1442. ////////////////////////////////////
  1443.  
  1444.  
  1445.  
  1446. var YOONO_BKM = new Bookmarks();